use std::rc::Rc;

use cgmath::{Matrix4, SquareMatrix};

use crate::{
    material::attribute::Uniform,
    mesh::{Mesh, MeshInstance},
    shader::Shader,
    Brick, Context, Material, Scene,
};

use super::{builtin_shaders, vertices::Vertex3DC};

pub struct ShapeBatch {
    pipeline: Rc<wgpu::RenderPipeline>,
    material: Material,
    mesh: MeshInstance<Vertex3DC>,
}

impl ShapeBatch {
    pub fn new(ctx: &Context, scene: &Scene) -> Self {
        let uniform = Uniform::<Matrix4<f32>>::new(ctx, Matrix4::identity());

        let shader = Shader::from_src::<Vertex3DC>(ctx, builtin_shaders::COLOR_3D_SRC);

        let material = Material::builder()
            .attr(uniform, wgpu::ShaderStages::VERTEX)
            .build(ctx);

        let layout =
            ctx.create_pipeline_layout(&[scene.global_layout(0).unwrap(), material.layout()]);

        let pipeline = ctx.create_simple_render_pipeline(
            &layout,
            &shader,
            wgpu::PrimitiveState {
                front_face: wgpu::FrontFace::Cw,
                cull_mode: Some(wgpu::Face::Back),
                ..Default::default()
            },
        );

        Self {
            pipeline: Rc::new(pipeline),
            mesh: MeshInstance::new(ctx),
            material,
        }
    }

    pub fn mesh(&self) -> &Mesh<Vertex3DC> {
        &self.mesh
    }

    pub fn mesh_mut(&mut self) -> &mut Mesh<Vertex3DC> {
        &mut self.mesh
    }

    pub fn material(&self) -> &Material {
        &self.material
    }

    pub fn material_mut(&mut self) -> &mut Material {
        &mut self.material
    }
}

impl Brick for ShapeBatch {
    fn prepare(&mut self, ctx: &crate::Context) {
        self.material.prepare(ctx);
        self.mesh.prepare(ctx);
    }

    fn render<'pass>(&'pass self, pass: &mut wgpu::RenderPass<'pass>) {
        pass.set_pipeline(&self.pipeline);
        pass.set_bind_group(1, self.material.group(), &[]);
        self.mesh.render(pass);
    }
}
